using System;
using Microsoft.Xna.Framework;

namespace Oops.Xna.Framework
{
	/// <summary>
	/// Regulates update frequencies on game components.
	/// </summary>
	public sealed class UpdateRegulator
	{
		private static Random random = new Random();

		private TimeSpan updateFrequency = TimeSpan.Zero;
		private TimeSpan nextUpdate;
		private bool reset = true;

		/// <summary>
		/// Initializes a new instance of the <see cref="UpdateRegulator"/> class.
		/// </summary>
		public UpdateRegulator()
		{
			// Default the variance.
			this.Variance = TimeSpan.FromMilliseconds(10.0);
		}

		/// <summary>
		/// Gets or sets the update frequency of the <see cref="UpdateRegulator"/>.
		/// </summary>
		public TimeSpan UpdateFrequency 
		{
			get { return this.updateFrequency; }
			set
			{
				if (this.updateFrequency != value)
				{
					this.updateFrequency = value;
					this.reset = true;
				}
			}
		}

		/// <summary>
		/// Gets or sets the update frequency variance of the <see cref="UpdateRegulator"/>.
		/// </summary>
		public TimeSpan Variance { get; set; }
		
		/// <summary>
		/// Determines the <see cref="UpdateRegulator"/> status.
		/// </summary>
		/// <param name="gameTime">The source <see cref="GameTime"/>.</param>
		/// <returns><b>true</b> if the updates can proceed. <b>false</b> if otherwise.</returns>
		public bool CanUpdate(GameTime gameTime)
		{
			// Zero signifies no regulation.
			if (this.updateFrequency == TimeSpan.Zero)
			{
				return true;
			}

			// Less than zero signifies always regulated.
			if (this.updateFrequency < TimeSpan.Zero)
			{
				return false;
			}

			// Determine if a the next update time should be calculated.
			if (this.reset || gameTime.TotalGameTime >= this.nextUpdate)
			{
				// Get the variance in milliseconds.
				double variance = this.Variance.TotalMilliseconds;

				// Calculate the next update time.
				this.nextUpdate = 
					gameTime.TotalGameTime + 
					this.updateFrequency + 
					TimeSpan.FromMilliseconds(UpdateRegulator.random.NextDouble(-variance, variance));

				// Resets don't cause updates.
				if (this.reset)
				{
					this.reset = false;
					return false;
				}

				// An update can occur.
				return true;
			}
			
			// No update should occur.
			return false;
		}
	}
}